package idv.takeshi.software.productline.bookstore.presentation.web.controller;

import idv.takeshi.software.productline.bookstore.application.service.MaintainUserAccountsService;
import idv.takeshi.software.productline.bookstore.application.service.UserAccountAlreadyActivatedException;
import idv.takeshi.software.productline.bookstore.application.service.UserRegistrationSerivce;
import idv.takeshi.software.productline.bookstore.application.service.dto.IndividualAccountDto;
import idv.takeshi.software.productline.bookstore.application.service.dto.MultiUserAccountDto;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.IndividualAccount;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.OrganizationAccount;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.RoleDuplicateException;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.Sex;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.UserAccount;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.UserAccountDuplicateException;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.UserAccountException;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.UserAccountFactory;

import java.util.Locale;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * A Controller for maintaining user accounts operations.
 * @author takeshi
 *
 */
@Controller("maintainAccountsController")
@RequestMapping(value = "/accounts")
public class MaintainAccountsController {
	
	private static final Log log = LogFactory.getLog(MaintainAccountsController.class);
	
	private static final String UPDATE_ACTION_NAME = "update";

	private static final String CREATE_ACTION_NAME = "create";

	private static final String USER_ACCOUNT_DTO_NAME = "userAccountDto";

	@Autowired
	private MaintainUserAccountsService maintainUserAccountsService;
	
	private UserRegistrationSerivce userRegistrationSerivce;
	
	@Autowired
	private UserAccountFactory userAccountFactory;
	
	@Autowired
	private MessageSource messageSource;
	
	@RequestMapping(value = "/listAccounts.html", method = RequestMethod.GET)
	public void listAccounts(
			@RequestParam(required = false) String accountName,
			@RequestParam(required = false) String role,
			@RequestParam(required = false, value = "idvOrOrg") String accountType,
			ModelMap modelMap,
			HttpServletRequest request){
		Set<UserAccount> accounts = null;
		String previousPath = null;
		accounts = this.maintainUserAccountsService.getUserAccountsByRoleAccountNameAndAccountType(
				role, accountName, accountType);
		
		previousPath = request.getServletPath();
		previousPath += StringUtils.isNotBlank(request.getQueryString())? "?" + request.getQueryString(): "";
		modelMap.addAttribute("accounts", accounts);
		modelMap.addAttribute("previousPath", previousPath);
	}
	
	@RequestMapping(value = "/maintainAccount.html", method = RequestMethod.GET)
	public void createUserAccountDto(
			@RequestParam String action,
			@RequestParam String previousPath,
			@RequestParam(required = false) Long accountId,
			ModelMap modelMap){
		MultiUserAccountDto userAccountDto = null;
		
		if(CREATE_ACTION_NAME.equals(action)){
			userAccountDto = new MultiUserAccountDto();
		} else if(UPDATE_ACTION_NAME.equals(action)){
			UserAccount userAccount = null;
			userAccount = this.maintainUserAccountsService.getUserAccountById(accountId);
			userAccountDto = new MultiUserAccountDto(userAccount);
		} else{
			throw new RuntimeException("action:" + action + " is not defined");
		}
		userAccountDto.setPreviousPath(previousPath);
		userAccountDto.setAction(action);
		modelMap.addAttribute(USER_ACCOUNT_DTO_NAME, userAccountDto);
	}
	
	@RequestMapping(value = "/create.html", method = RequestMethod.POST)
	public String addUserAccount(
			@ModelAttribute(USER_ACCOUNT_DTO_NAME) @Valid final MultiUserAccountDto userAccountDto,
			final BindingResult result, final Locale locale, ModelMap modelMap) throws Exception {
		
		if(result.hasErrors())
			return "/accounts/maintainAccount";
		checkUserAccountDto(userAccountDto, result, locale);
		if(result.hasErrors())
			return "/accounts/maintainAccount";
		
		UserAccount userAccount = null;
		try {
			userAccount = userAccountDto.createModel(userAccountFactory);
			this.userRegistrationSerivce.addUserAccountAndSendMail(userAccount, locale, "dummy");
		} catch (UserAccountDuplicateException e) {
			log.warn("add user account:" + userAccount.getAccountName() + " failed");
			result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "accountName", 
					messageSource.getMessage("Pattern.userAccountDto.accountName.duplicate", 
					new Object[]{userAccount.getAccountName()}, locale)));
			return "/accounts/maintainAccount";
		} catch(Exception e){
			log.error("add user account error", e);
			throw e;
		}
		
		//TODO TBD
		return "redirect:" + userAccountDto.getPreviousPath();
	}
	
	@RequestMapping(value = "/update.html", method = RequestMethod.POST)
	public String updateUserAccount(
			@ModelAttribute(USER_ACCOUNT_DTO_NAME) @Valid final MultiUserAccountDto userAccountDto,
			final BindingResult result, final Locale locale, ModelMap modelMap) throws RoleDuplicateException{
		
		if(result.hasErrors())
			return "/accounts/maintainAccount";
		checkUserAccountDto(userAccountDto, result, locale);
		if(result.hasErrors())
			return "/accounts/maintainAccount";
		
//		UserAccount userAccount = null;
//		userAccount = this.maintainUserAccountsService.getUserAccountById(userAccountDto.getId());
//		userAccountDto.loadModel(userAccount);
		
		this.maintainUserAccountsService.updateUserAccount(userAccountDto);
		
		//TODO TBD
		return "redirect:" + userAccountDto.getPreviousPath();
	}
	
	/**
	 * @return the maintainUserAccountsService
	 */
	public MaintainUserAccountsService getMaintainUserAccountsService() {
		return maintainUserAccountsService;
	}

	/**
	 * @param maintainUserAccountsService the maintainUserAccountsService to set
	 */
	public void setMaintainUserAccountsService(
			MaintainUserAccountsService maintainUserAccountsService) {
		this.maintainUserAccountsService = maintainUserAccountsService;
	}

	/**
	 * @return the userRegistrationSerivce
	 */
	public UserRegistrationSerivce getUserRegistrationSerivce() {
		return userRegistrationSerivce;
	}

	/**
	 * @param userRegistrationSerivce the userRegistrationSerivce to set
	 */
	public void setUserRegistrationSerivce(
			UserRegistrationSerivce userRegistrationSerivce) {
		this.userRegistrationSerivce = userRegistrationSerivce;
	}

	/**
	 * check rules for userAccountDto
	 * @param userAccountDto
	 */
	private void checkUserAccountDto(MultiUserAccountDto userAccountDto, BindingResult result, Locale locale){
		Validate.notNull(userAccountDto, "userAccountDto shall always not be null");
		Validate.notNull(result, "result shall always not be null");
		Validate.notNull(locale, "locale shall always not be null");
		
		if(!StringUtils.equals(userAccountDto.getPassword(), userAccountDto.getConfirmedPassword()))
			result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "confirmedPassword", 
					messageSource.getMessage("Pattern.userAccountDto.confirmedPassword.notEquals", null, locale)));
		
		if(ArrayUtils.isEmpty(userAccountDto.getRoles()))
			result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "roles", 
					messageSource.getMessage("Pattern.userAccountDto.roles", null, locale)));
		
		if(CREATE_ACTION_NAME.equals(userAccountDto.getAction())){
			if(StringUtils.length(userAccountDto.getPassword()) < 5)
				result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "password", 
						messageSource.getMessage("Pattern.userAccountDto.password", null, locale)));
			
			if(StringUtils.length(userAccountDto.getConfirmedPassword()) < 5)
				result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "confirmedPassword", 
						messageSource.getMessage("Pattern.userAccountDto.confirmedPassword", null, locale)));
			
			if(!StringUtils.equals(userAccountDto.getPassword(), userAccountDto.getConfirmedPassword()))
				result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "confirmedPassword", 
						messageSource.getMessage("Pattern.userAccountDto.confirmedPassword.notEquals", null, locale)));
			
		} else if(UPDATE_ACTION_NAME.equals(userAccountDto.getAction())){
			if(null == userAccountDto.getId())
				result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "id", 
						messageSource.getMessage("Pattern.userAccountDto.id", null, locale)));
			
			if(StringUtils.isNotBlank(userAccountDto.getNewPassword()) && 
					StringUtils.isNotBlank(userAccountDto.getConfirmedNewPassword())){
				if(StringUtils.length(userAccountDto.getNewPassword()) < 5)
					result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "newPassword", 
							messageSource.getMessage("Pattern.userAccountDto.newPassword", null, locale)));
				
				if(StringUtils.length(userAccountDto.getConfirmedNewPassword()) < 5)
					result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "confirmedNewPassword", 
							messageSource.getMessage("Pattern.userAccountDto.confirmedNewPassword", null, locale)));
				
				if(!StringUtils.equals(userAccountDto.getNewPassword(), userAccountDto.getConfirmedNewPassword()))
					result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "confirmedNewPassword", 
							messageSource.getMessage("Pattern.userAccountDto.confirmedNewPassword.notEquals", null, locale)));
			}
			
		} else {
			result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "action", 
					messageSource.getMessage("Pattern.userAccountDto.action", 
					new Object[]{userAccountDto.getAction()}, locale)));
		}
		
		if(IndividualAccount.class.getName().equals(userAccountDto.getUserAccountType())){
			if(StringUtils.isBlank(userAccountDto.getFirstName()))
				result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "firstName", 
						messageSource.getMessage("Pattern.userAccountDto.firstName", null, locale)));
			
			if(StringUtils.isBlank(userAccountDto.getLastName()))
				result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "lastName", 
						messageSource.getMessage("Pattern.userAccountDto.lastName", null, locale)));
			
			try{
				Sex.valueOf(userAccountDto.getSex());
			} catch(Exception e){
				result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "sex", 
						messageSource.getMessage("Pattern.userAccountDto.sex", null, locale)));
			}
			
			if(null == userAccountDto.getBirthday())
				result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "birthday", 
						messageSource.getMessage("Pattern.userAccountDto.birthday", null, locale)));
			
			
		} else if(OrganizationAccount.class.getName().equals(userAccountDto.getUserAccountType())){
			if(StringUtils.isBlank(userAccountDto.getName()))
				result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "name", 
						messageSource.getMessage("Pattern.userAccountDto.name", null, locale)));
			
		} else {
			result.addError(new FieldError(USER_ACCOUNT_DTO_NAME, "userAccountType", 
					messageSource.getMessage("Pattern.userAccountDto.userAccountType.notDefined", 
					new Object[]{userAccountDto.getUserAccountType()}, locale)));
		}
		
		
	}

}
